package com.example.nettydemo.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

/**
 * Netty Server 服务端
 *
 * @author SuperWein
 */
@Slf4j
public class NettyServer {
    // 默认绑定端口号
    private int port = 8888;
    // 默认读超时时间
    private long readIdelTimeOut = 16L;
    // 默认写超时时间
    private long writeIdelTimeOut = 10L;
    // 所有超时
    private long allIdelTimeOut = 20L;
    //传输消息最大长度
    private int messageMaxSize = 1024;
    // 自定义分隔符
    private final String ascii2 = String.valueOf((char) 2);
    private final String ascii3 = String.valueOf((char) 3);

    public NettyServer() {}

    /**
     * 绑定端口号
     *
     * @param port
     * @return
     */
    public NettyServer bind(int port) {
        this.port = port > 0 ? port : this.port;
        return this;
    }

    /**
     * 设置读超时时间
     *
     * @param readIdelTimeOut
     * @return
     */
    public NettyServer readIdelTimeOut(long readIdelTimeOut) {
        this.readIdelTimeOut = readIdelTimeOut > 0 ? readIdelTimeOut : this.readIdelTimeOut;
        return this;
    }

    /**
     * 设置所有超时时间
     *
     * @param allIdelTimeOut
     * @return
     */
    public NettyServer allIdelTimeOut(long allIdelTimeOut) {
        this.allIdelTimeOut = allIdelTimeOut > 0 ? allIdelTimeOut : this.allIdelTimeOut;
        return this;
    }

    /**
     * 设置写超时时间
     *
     * @param writeIdelTimeOut
     * @return
     */
    public NettyServer writeIdelTimeOut(long writeIdelTimeOut) {
        this.writeIdelTimeOut = writeIdelTimeOut > 0 ? writeIdelTimeOut : this.writeIdelTimeOut;
        return this;
    }

    /**
     * 设置消息最大数量
     *
     * @param messageMaxSize
     * @return
     */
    public NettyServer messageMaxSize(int messageMaxSize) {
        this.messageMaxSize = messageMaxSize > 0 ? messageMaxSize : this.messageMaxSize;
        return this;
    }

    /**
     * 启动Netty服务
     */
    public void start() {
        // 定义server启动类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 定义工作组:boss负责监听端口请求，分发请求给各个worker; worker负责处理请求（读写）
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 设置工作组和通道Channel
            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    // 连接缓冲池的大小
                    .option(ChannelOption.SO_BACKLOG, messageMaxSize)
                    // 维持链接的活跃，清除死链接
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 关闭延迟发送
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 添加handler，管道中的处理器，通过ChannelInitializer来构造
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            // 此方法每次客户端连接都会调用，是为通道初始化的方法
                            channel.pipeline().addLast(new IdleStateHandler(readIdelTimeOut,
                                    writeIdelTimeOut, allIdelTimeOut, TimeUnit.SECONDS));
                            channel.pipeline().addLast(new HeartBeatHandler());
                            // 利用DelimiterBasedFrameDecoder解决TCP的粘包/拆包的问题
                            // 如果一次性发送内容超过缓冲区大小会报错，这里的参数maxFrameLength要根据实际情况配置
                            channel.pipeline().addLast(new DelimiterBasedFrameDecoder(2048,
                                    false, false, Unpooled.copiedBuffer(ascii3.getBytes())));
                            // 设置读写编码
                            channel.pipeline().addLast(new StringEncoder(Charset.forName("utf-8")));
                            channel.pipeline().addLast(new StringDecoder(Charset.forName("utf-8")));
                            // 可以设置多个channel handler, 属于责任链模式，依次按设置顺序调用
                            // 在channel handle实现类中，处理管道读事件，同时可以添加写入内容到客户端
                            channel.pipeline().addLast(new ChannelServerHandler());
                        }
                    });
            // 绑定端口号，启动Netty服务端
            ChannelFuture future = serverBootstrap.bind(port).sync();
            log.info("Netty Server is startup with port: {}", port);
            // 关闭监听 Wait until the server socket is closed.
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("Netty Server Thread InterruptedException.", e);
        } finally {
            // 关闭资源 Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}
